﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Windows.Forms;

using OpenTK.Graphics.OpenGL;

using PokeEditor.globals;

namespace PokeEditor.TileEditor
{
    public enum MapPosition
    {
        POSITION_NW = 0,
        POSITION_NE = 1,
        POSITION_SW = 2,
        POSITION_SE = 3,
    }

    public struct Key
    {
        public readonly int Dimension1;
        public readonly int Dimension2;
        public Key(int p1, int p2)
        {
            Dimension1 = p1;
            Dimension2 = p2;
        }
    }

    public class TileEngine
    {
        public frmEditor MyParent;
        RenderWorld render;

        public Hashtable _selectedTiles = new Hashtable();
        public Hashtable _selectedMoveTiles = new Hashtable();
        TilePointTable mapDataTable;

        /// <summary>
        /// Constructor
        /// </summary>
        public TileEngine(frmEditor parent)
        {
            MyParent = parent;
            render = new RenderWorld(this);
        }

        #region Public Members

        private int _MapHeight;
        public int MapHeight // Hight of control
        {
            set
            {
                _MapHeight = value;
            }
            get
            {
                return _MapHeight;
            }
        }

        private int _MapWidth;
        public int MapWidth // width of control
        {
            set
            {
                _MapWidth = value;
            }
            get
            {
                return _MapWidth;
            }
        }

        private int _mapBeginX;
        public int mapBeginX
        {
            set
            {
                _mapBeginX = value;
            }
            get
            {
                return _mapBeginX;
            }
        }

        private int _mapBeginY;
        public int mapBeginY
        {
            set
            {
                _mapBeginY = value;
            }
            get
            {
                return _mapBeginY;
            }
        }

        private MapPosition _mapPosition = MapPosition.POSITION_SE;
        public MapPosition mapPosition
        {
            set
            {
                _mapPosition = value;
            }
            get
            {
                return _mapPosition;
            }
        }

        public int TileSize
        {
            set
            {
                MyParent.m_AppData.mapGridSize = value;
            }
            get
            {
                return MyParent.m_AppData.mapGridSize;
            }
        }

        private int _topOffset = 0;
        public int screenTopOffset
        {
            get
            {
                return _topOffset;
            }
            set
            {
                _topOffset = value;
            }
        }

        private int _sideOffset = 0;
        public int screenSideOffset
        {
            get
            {
                return _sideOffset;
            }
            set
            {
                _sideOffset = value;
            }
        }

        float _screenX = 0.0f;       // Translate screen to x direction (left or right)        
        public float screenLocationX
        {
            set
            {
                _screenX = value;
            }
            get
            {
                return _screenX;
            }
        }

        float _screenY = 0.0f;       // Translate screen to y direction (up or down)
        public float screenLocationY
        {
            set
            {
                _screenY = value;
            }
            get
            {
                return _screenY;
            }
        }

        #endregion

        #region PUBLIC FUNCTIONS

        /// <summary>
        /// Calculates the Tile from the x and y-coordinates that are pixel-based.
        /// </summary>
        /// <param name="x">The x-coordinate.</param>
        /// <param name="y">The y-coordinate.</param>
        /// <returns></returns>
        public TilePoint GetTileFromXY(int x, int y)
        {
            TilePoint tp = new TilePoint() { IsNull = false };

            int gridOffset = (AppData.getInstance().showGrid) ? 1 : 0;

            if (mapPosition == MapPosition.POSITION_SE) // Default
            {
                tp.X = ((screenSideOffset + x) / (TileSize + gridOffset)) + mapBeginX;
                tp.Y = ((screenTopOffset + y) / (TileSize + gridOffset)) + mapBeginY;
            }
            else if (mapPosition == MapPosition.POSITION_SW)
            {
                tp.X = 0 - ((screenSideOffset - (0 - MapWidth) - x) / (TileSize + gridOffset)) + mapBeginX;
                tp.Y = ((screenTopOffset + y) / (TileSize + gridOffset)) + mapBeginY;
            }
            else if (mapPosition == MapPosition.POSITION_NE)
            {
                tp.X = ((screenSideOffset + x) / (TileSize + gridOffset)) + mapBeginX;
                tp.Y = 0 - ((screenTopOffset - (0 - MapHeight) - y) / (TileSize + gridOffset)) + mapBeginY;
            }
            else if (mapPosition == MapPosition.POSITION_NW)
            {
                tp.X = 0 - ((screenSideOffset - (0 - MapWidth) - x) / (TileSize + gridOffset)) + mapBeginX;
                tp.Y = 0 - ((screenTopOffset - (0 - MapHeight) - y) / (TileSize + gridOffset)) + mapBeginY;
            }

            return tp;
        }

        public float[] ResetMapPosition()
        {
            float[] returnValue = new float[2];

            if (mapPosition == MapPosition.POSITION_SW)
            {
                returnValue[0] = MapWidth;
                returnValue[1] = 0.0f;
            }
            else if (mapPosition == MapPosition.POSITION_NE)
            {
                returnValue[0] = 0.0f;
                returnValue[1] = MapHeight;
            }
            else if (mapPosition == MapPosition.POSITION_NW)
            {
                returnValue[0] = MapWidth;
                returnValue[1] = MapHeight;
            }
            else
            {
                returnValue[0] = 0.0f;
                returnValue[1] = 0.0f;
            }

            return returnValue;
        }

        /// <summary>
        /// Resize the Opengl window
        /// </summary>
        /// <param name="Height"></param>
        /// <param name="Width"></param>
        public void SetMapSize(int Height, int Width, MapPosition position)
        {
            MapHeight = Height;
            MapWidth = Width;
            mapPosition = position;

            float[] tmp = ResetMapPosition();
            screenLocationX = tmp[0];
            screenLocationY = tmp[1];
            screenSideOffset = 0;
            screenTopOffset = 0;

            render.SetMapSize(Height, Width);

            mapDataTable = new TilePointTable();
        }

        /// <summary>
        /// Update glTranslatef (Scrolling)
        /// </summary>
        /// <param name="ViewX"></param>
        /// <param name="ViewY"></param>
        public void SetMapView(float ViewX, float ViewY)
        {
            if (ViewX == 0.0f && ViewY == 0.0f)
                return;

            screenLocationX -= ViewX;
            screenLocationY -= ViewY;

            if (mapPosition == MapPosition.POSITION_SE)
            {
                screenSideOffset += (int)ViewX;
                screenTopOffset += (int)ViewY;
            }
            else if (mapPosition == MapPosition.POSITION_SW)
            {
                screenSideOffset -= (int)ViewX;
                screenTopOffset += (int)ViewY;
            }
            else if (mapPosition == MapPosition.POSITION_NE)
            {
                screenSideOffset += (int)ViewX;
                screenTopOffset -= (int)ViewY;
            }
            else if (mapPosition == MapPosition.POSITION_NW)
            {
                screenSideOffset -= (int)ViewX;
                screenTopOffset -= (int)ViewY;
            }

            if (screenSideOffset < 0)
            {
                float[] returnValue = ResetMapPosition();
                screenLocationX = returnValue[0];
                screenSideOffset = 0;
            }

            if (screenTopOffset < 0)
            {
                float[] returnValue = ResetMapPosition();
                screenLocationY = returnValue[1];
                screenTopOffset = 0;
            }
        }

        public void DoRender(TilePointTable mapData)
        {
            DoRender(mapData, true);
        }

        public void DoRender(TilePointTable mapData, bool fullRender) // TilePoint Struct/Rect whatever
        {
            render.RenderScreen(mapData, fullRender);
        }

        #region SELECTION FUNCTIONS
        /// <summary>
        /// Check if a tile already is selected
        /// </summary>
        /// <param name="x">X Coordinate</param>
        /// <param name="y">Y Coordinate</param>
        /// <returns></returns>
        public bool SelectionCheck(int x, int y)
        {
            return SelectionCheck(new Key(x, y));
        }
        /// <summary>
        /// Check if a tile already is selected
        /// </summary>
        /// <param name="selectKey">Selection Key</param>
        /// <returns></returns>
        public bool SelectionCheck(Key selectKey)
        {
            return _selectedTiles.ContainsKey(selectKey);
        }

        /// <summary>
        /// Clear selection
        /// </summary>
        public void SelectionDeleteAll()
        {
            _selectedTiles.Clear();
        }

        /// <summary>
        /// Delete tile from selection
        /// </summary>
        /// <param name="x">X Coordinate</param>
        /// <param name="y">Y Coordinate</param>
        public void SelectionDelete(int x, int y)
        {
            SelectionDelete(new Key(x, y));
        }

        /// <summary>
        /// Delete tile from selection
        /// </summary>
        /// <param name="selectKey">Selection Key</param>
        public void SelectionDelete(Key selectKey)
        {
            if (SelectionCheck(selectKey))
                _selectedTiles.Remove(selectKey);
        }

        /// <summary>
        /// Add new tile to selection
        /// </summary>
        /// <param name="tp">TilePoint with coordinate info</param>
        public void SelectionAdd(TilePoint tp)
        {
            SelectionAdd(tp.X, tp.Y);
        }

        /// <summary>
        /// Add new tile to selection
        /// </summary>
        /// <param name="x">X Coordinate</param>
        /// <param name="y">Y Coordinate</param>
        public void SelectionAdd(int x, int y)
        {
            Key hashKey = new Key(x, y);

            if (!SelectionCheck(hashKey))
                _selectedTiles.Add(hashKey, null);
            else
                _selectedTiles.Remove(hashKey);
        }

        /// <summary>
        /// Selects a rectangle using the start, last and current tile information.
        /// </summary>
        /// <param name="start">The starting tile for the selection.</param>
        /// <param name="last">The previous tile the mouse was on.</param>
        /// <param name="now">The tile that the mouse is now currently on.</param>
        public void SelectionRectangle(TilePoint start, TilePoint last, TilePoint now)
        {
            int LoX = Math.Min(start.X, now.X);
            int LoY = Math.Min(start.Y, now.Y);
            int HiX = Math.Max(start.X, now.X);
            int HiY = Math.Max(start.Y, now.Y);

            for (int y = Math.Min(LoY, last.Y); y <= Math.Max(HiY, last.Y); y++)
            {
                for (int x = Math.Min(LoX, last.X); x <= Math.Max(HiX, last.X); x++)
                {
                    if ((x >= LoX && x <= HiX && y >= LoY && y <= HiY))
                    {
                        if (!SelectionCheck(x, y))
                            SelectionAdd(x, y);
                    }
                    else if (SelectionCheck(x, y))
                        SelectionDelete(new Key(x, y)); //Reset back to last saved selection
                }
            }
        }

        public void SelectionMove(int BeginPosition_X, int BeginPosition_Y, int NowPosition_X, int NowPosition_Y)
        {
            // Calculate side to move
            int X_Diff = NowPosition_X - BeginPosition_X;
            int Y_Diff = NowPosition_Y - BeginPosition_Y;

            _selectedMoveTiles.Clear();

            foreach (Key tileKey in _selectedTiles.Keys)
            {
                // Calulate new X, Y Coordinates
                int newX = tileKey.Dimension1 + X_Diff;
                int newY = tileKey.Dimension2 + Y_Diff;

                // Add to move hashtable
                _selectedMoveTiles.Add(new Key(newX, newY), null);
            }
        }

        public void SelectionMoveFinall(int BeginPosition_X, int BeginPosition_Y, int EndPosition_X, int EndPosition_Y)
        {
            // Calculate side to move - X
            int X_Diff = EndPosition_X - BeginPosition_X;
            int Y_Diff = EndPosition_Y - BeginPosition_Y;

            // No differance
            if (X_Diff == 0 && Y_Diff == 0)
            {
                // Clear move arraylist
                _selectedMoveTiles.Clear();
                return;
            }

            //TODO
            //this doesn't work I think
            TilePointTable myData = new TilePointTable(MyParent.mapData);
            foreach (Key index in _selectedTiles.Keys)
            {
                TilePointTable tmpData = myData;
                int X_old = index.Dimension1;
                int Y_old = index.Dimension2;

                int X_new = X_old + X_Diff;
                int Y_new = Y_old + Y_Diff;

                //tmpView.RowFilter = String.Format("X = {0} AND Y = {1}", X_old, Y_old);
                tmpData = tmpData.GetTilePoints(X_old, Y_old);
                foreach (TilePoint myTilePoint in tmpData.Values)
                {
                    TilePoint newTilePoint = new TilePoint(myTilePoint);
                    newTilePoint.X = X_new;
                    newTilePoint.Y = Y_new;

                    mapDataTable.AddTilePoint(newTilePoint);

                    // Remove old TilePoint
                    TilePoint tmp = MyParent.mapData.GetTilePoint(X_old, Y_old, myTilePoint.Z);
                    MyParent.mapData.RemoveTilePoint(tmp);
                }

                tmpData.Clear();
            }

            if (mapDataTable.Count > 0)
            {
                MyParent.mapData.Merge(mapDataTable);
                mapDataTable.Clear();
            }

            // Clear move arraylist
            _selectedTiles.Clear();
            foreach (DictionaryEntry entry in _selectedMoveTiles)
            {
                _selectedTiles.Add(entry.Key, null);
            }
            _selectedMoveTiles.Clear();
        }

        #endregion

        #endregion
    }

    public class RenderWorld
    {
        TileEngine MyParent;
        TextureManager textManager;
        AppData appInstance = AppData.getInstance();

        bool showGrid = true;

        public RenderWorld(TileEngine parent)
        {
            MyParent = parent;
            textManager = TextureManager.getInstance();

            GL.Enable(EnableCap.Texture2D);
            GL.Enable(EnableCap.ColorMaterial);
            GL.Disable(EnableCap.DepthTest);

            GL.Enable(EnableCap.AlphaTest);
            GL.AlphaFunc(AlphaFunction.Greater, 0.0f);

            GL.Disable(EnableCap.CullFace);
            GL.Enable(EnableCap.Blend);

            GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
            GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
        }

        public void SetMapSize(int height, int width)
        {
            // Set viewport to window dimensions.
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();

            GL.Viewport(0, 0, width, height);
            GL.Ortho(0, width, height, 0, 0, 1);

            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();
        }


        public void RenderScreen(TilePointTable mapData, bool renderAll)
        {
            showGrid = AppData.getInstance().showGrid;

            // Clear Screen And Depth Buffer
                GL.Clear(ClearBufferMask.ColorBufferBit);
                GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);

                GL.PushMatrix(); // It is important to push
            // the Matrix before calling glRotatef and glTranslatef

            GL.Translate(MyParent.screenLocationX, MyParent.screenLocationY, 0); // Translates the screen

            if (showGrid)
            {
                // Render background (grid)
                RenderWorldGrid();
            }

            // Render placed tiles
            RenderPlacedTiles(mapData, renderAll);

            // Render selected tiles
            RenderSelectedTiles();

            // Render active tile/deployable
            if (appInstance.editorPointer == 0 && appInstance.KeyDown_Ctrl)
                RenderActiveMouseTile();

            GL.PopMatrix();  // Don't forget to pop the Matrix

            //OpenTK.Graphics.GL.Flush();
            GL.Finish();
        }

        private void RenderWorldGrid()
        {
            textManager.BindTexture("0");

            int mapTileSize = MyParent.TileSize;
            MapPosition position = MyParent.mapPosition;

            int Tiles_H = (MyParent.MapWidth + MyParent.screenSideOffset) / mapTileSize;
            int Tiles_V = (MyParent.MapHeight + MyParent.screenTopOffset) / mapTileSize;

            float top = 0.0f, left = 0.0f;
            float width = 0.0f, height = 0.0f;

            for (int y = 1; y <= Tiles_V; y++)
            {
                for (int x = 1; x <= Tiles_H; x++)
                {
                    if (MyParent.mapPosition == MapPosition.POSITION_SE)
                    {
                        left = (x * mapTileSize) - mapTileSize + ((x > 1) ? ((x * 1) - 1) : 0);
                        top = (y * mapTileSize) - mapTileSize + ((y > 1) ? ((y * 1) - 1) : 0);
                    }
                    else if (MyParent.mapPosition == MapPosition.POSITION_SW)
                    {
                        int x2 = 0 - x;
                        left = (x2 * mapTileSize) + ((x2 < -1) ? ((x2 * 1) + 1) : 0);
                        top = (y * mapTileSize) - mapTileSize + ((y > 1) ? ((y * 1) - 1) : 0);
                    }
                    else if (MyParent.mapPosition == MapPosition.POSITION_NE)
                    {
                        left = (x * mapTileSize) - mapTileSize + ((x > 1) ? ((x * 1) - 1) : 0);
                        int y2 = 0 - y;
                        top = (y2 * mapTileSize) + ((y2 < -1) ? ((y2 * 1) + 1) : 0);
                    }
                    else
                    {
                        int x2 = 0 - x;
                        left = (x2 * mapTileSize) + ((x2 < -1) ? ((x2 * 1) + 1) : 0);
                        int y2 = 0 - y;
                        top = (y2 * mapTileSize) + ((y2 < -1) ? ((y2 * 1) + 1) : 0);
                    }

                    width = left + mapTileSize;
                    height = top + mapTileSize;

                    GL.Begin(BeginMode.Quads);
                    GL.Vertex2(width, height);
                    GL.Vertex2(left, height);
                    GL.Vertex2(left, top);
                    GL.Vertex2(width, top);
                    GL.End();
                }
            }
        }

        private void RenderPlacedTiles(TilePointTable mapData, bool renderAll)
        {
            //DataTable masterView = mapData.ToTable();

            if (!renderAll) // Reset tiles
            {
                foreach (DataRowView currentTile in mapData.Values)
                {
                    int tmpTileX = (int)currentTile["X"];
                    int tmpTileY = (int)currentTile["Y"];

                    RenderTile(tmpTileX, tmpTileY, "0", -999, -999, false);
                }
            }

            // Render tiles
            for (int i = 0; i < 3; i++)
            {
                if (!appInstance.showLayers[i]) { continue; }

                //DataView tmpView = new DataView(masterView);
                //tmpView.RowFilter = "Z = " + i;

                foreach (TilePoint currentTile in mapData.Values)
                {
                    if (currentTile.Z != i)
                        continue;

                    int tmpTileX = currentTile.X;
                    int tmpTileY = currentTile.Y;
                    string tmpTileT = currentTile.T;
                    int tmpTileM = -999;
                    int tmpTileE = -999;

                    // Check if movement layer is visible
                    if (appInstance.showLayers[3])
                        tmpTileM = currentTile.M;
                    // Check if event layer is visible
                    else if (appInstance.showLayers[4] && i == 1)
                        tmpTileE = currentTile.E;

                    RenderTile(tmpTileX, tmpTileY, tmpTileT, tmpTileM, tmpTileE, false);
                }
            }
        }

        private void RenderSelectedTiles()
        {
            foreach (Key index in MyParent._selectedTiles.Keys)
            {
                RenderTile(index.Dimension1, index.Dimension2, "0", 0, 0, true);
            }

            if (AppData.getInstance().showLayers[5])
            {
                foreach (Key index in MyParent._selectedMoveTiles.Keys)
                {
                    RenderTile(index.Dimension1, index.Dimension2, "1", 0, 0, true);
                }
            }
        }

        private void RenderActiveMouseTile()
        {
            if (appInstance.mousePosition.X == -1 || appInstance.mousePosition.Y == -1)
                return;

            if (appInstance.activeTileLeft == "-1") // Deployable
            {
                Deployable CurrentDeployable = appInstance.deployableLeft;

                int dHeight = CurrentDeployable.DeployHeight;
                int dWidth = CurrentDeployable.DeployWidth;

                string CurrentTile;

                int X_Location;
                int Y_Location = appInstance.mousePosition.Y;

                for (int h = 0; h < dHeight; h++)
                {
                    X_Location = appInstance.mousePosition.X;

                    for (int w = 0; w < dWidth; w++)
                    {
                        CurrentTile = CurrentDeployable[w, h];

                        if (CurrentTile != "0" || CurrentTile != "-1")
                            RenderTile(X_Location, Y_Location, CurrentTile, -999, -999, false);

                        X_Location++;
                    }

                    Y_Location++;
                }
            }
            else // Normal tile
            {
                RenderTile(appInstance.mousePosition.X, appInstance.mousePosition.Y, appInstance.activeTileLeft, -999, -999, false);
            }
        }

        private void RenderTile(int x, int y, string t, int m, int e, bool selected)
        {
            int mapTileSize = MyParent.TileSize;

            float left = 0.0f;
            float top = 0.0f;

            x -= (MyParent.mapBeginX);
            y -= (MyParent.mapBeginY);

            if (MyParent.mapPosition == MapPosition.POSITION_SE)
            {
                x += 1;
                y += 1;
                left = (x * mapTileSize) - mapTileSize + ((x > 1 && showGrid) ? (x - 1) : 0);
                top = (y * mapTileSize) - mapTileSize + ((y > 1 && showGrid) ? (y - 1) : 0);
            }
            else if (MyParent.mapPosition == MapPosition.POSITION_SW)
            {
                x -= 1;
                y += 1;
                left = (x * mapTileSize) + ((x < -1 && showGrid) ? (x + 1) : 0);
                top = (y * mapTileSize) - mapTileSize + ((y > 1 && showGrid) ? (y - 1) : 0);
            }
            else if (MyParent.mapPosition == MapPosition.POSITION_NE)
            {
                x += 1;
                y -= 1;
                left = (x * mapTileSize) - mapTileSize + ((x > 1 && showGrid) ? (x - 1) : 0);
                top = (y * mapTileSize) + ((y < -1 && showGrid) ? (y + 1) : 0);
            }
            else
            {
                x -= 1;
                y -= 1;
                left = (x * mapTileSize) + ((x < -1 && showGrid) ? (x + 1) : 0);
                top = (y * mapTileSize) + ((y < -1 && showGrid) ? (y + 1) : 0);
            }

            float width = left + mapTileSize;
            float height = top + mapTileSize;

            try
            {
                // Bind texture                
                if (selected)
                {
                    if (t == "1")
                        textManager.BindTexture("s1");
                    else
                        textManager.BindTexture("s0");
                }
                else
                {
                    if (e > -999)
                        textManager.BindTexture("e" + e.ToString());
                    else if (m > -999)
                        textManager.BindTexture("m" + m.ToString());
                    else
                        textManager.BindTexture(t);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Texture Binding Error");
                return;
            }

            GL.Begin(BeginMode.Quads);
            GL.TexCoord2(0, 0); GL.Vertex2(left, top);
            GL.TexCoord2(1, 0); GL.Vertex2(width, top);
            GL.TexCoord2(1, 1); GL.Vertex2(width, height);
            GL.TexCoord2(0, 1); GL.Vertex2(left, height);
            GL.End();
        }
    }
}

